package com.stottlerhenke.simbionic.editor;

import java.awt.Color;
import java.io.BufferedWriter;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JOptionPane;

import com.stottlerhenke.simbionic.api.SB_VarType;
import com.stottlerhenke.simbionic.common.parser.SB_ExpressionNode;
import com.stottlerhenke.simbionic.editor.gui.ComponentRegistry;
import com.stottlerhenke.simbionic.editor.gui.SB_BindingsHolder;
import com.stottlerhenke.simbionic.editor.gui.SB_Catalog;
import com.stottlerhenke.simbionic.editor.gui.SB_Drawable;
import com.stottlerhenke.simbionic.editor.gui.SB_Line;
import com.stottlerhenke.simbionic.editor.gui.SB_Output;
import com.stottlerhenke.simbionic.editor.gui.SB_OutputBar;
import com.stottlerhenke.simbionic.editor.gui.SB_Polymorphism;
import com.stottlerhenke.simbionic.editor.gui.SB_ProjectBar;
import com.stottlerhenke.simbionic.editor.gui.SB_TabbedCanvas;
import com.stottlerhenke.simbionic.editor.parser.SB_ErrorNode;
import com.stottlerhenke.simbionic.editor.parser.SB_ParseNode;
import com.stottlerhenke.simbionic.editor.parser.SB_ParserEditorInterface;

public class SB_Binding implements Externalizable
{
    private static final long serialVersionUID = 2302585093L + 2000;
    
    public final static String ACTION_BINDING = "(Action)";

    protected String _var;   // bound variable
    protected String _expr;  // binding expression

    public SB_Binding()  // constructor for Externalizable object
    {
    }

    public SB_Binding(String v, String e)
    {
        _var = v;
        _expr = e;
    }

    public String toString() { return _var; }  // for display in combo box

    public boolean equals(SB_Binding binding)
    {
      return _var.equals(binding._var) && _expr.equals(binding._expr);
    }

    /**
     * Determine if the var is an ACTION_BINDING
     * @return true if the binding is an action, false otherwise
     */
    public boolean isAction()
    {
    	return ACTION_BINDING.equals(_var);
    }
    public void compileSIM(BufferedWriter out, SB_Polymorphism poly, SB_Drawable drawable, SB_ErrorInfo errorInfo)
    {
      SimBionicEditor editor = poly.getParent().getEditor();
      SB_OutputBar outputBar = SB_OutputBar.getInstance();
      SB_Output build = outputBar.getBuild();

      try
      {
    	  SB_ProjectBar projectBar = ComponentRegistry.getProjectBar();
    	  
        SB_Catalog catalog = projectBar.getCatalog();
        
        SB_Variable var = catalog.findVariable(poly, _var);
        if (var == null)
        {
          errorInfo._ne++;
          build.addLine(new SB_Line("ERROR: Unknown variable '" + _var + "'.",
                                    Color.red, poly, drawable, this, SB_Line.ERROR));
          return;
        }

        int varKind = (var instanceof SB_Global ? 3 : 0);
        SB_VarType type = SB_TypeManager.convertOldTypeId(var.getType());

        // parse expression
        String expr = catalog.constantReplace(_expr);
        SB_ExpressionNode tree = SB_ParserEditorInterface.getParser().Parse(expr);
        SB_ParseNode ptree = (SB_ParseNode)( tree );
        if (!ptree.IsValid())
        {
          SB_ErrorNode err = null;
          while ((err = ptree.GetNextError(err)) != null)
          {
            errorInfo._ne++;
            build.addLine(new SB_Line("ERROR: " + err.GetMsg(), Color.red, poly, drawable, this, SB_Line.ERROR));
          }
        }
        else if (!projectBar.getTypeManager().canCastTypes(type, ptree.GetType()))
        {
            errorInfo._ne++;
            build.addLine(new SB_Line("ERROR: Binding expression returns wrong variable type.",
                                      Color.red, poly, drawable, this, SB_Line.ERROR));
        }
        
        // TODO to support binding expression to class member, bindings editor also needs to be changed to support class memeber
        
        out.write(varKind + " " + SB_ProjectBar.getPString(_var) + " "
                  + SB_ProjectBar.getPString(expr));
        out.newLine();
      }
      catch (IOException exception)
      {
        System.err.println("i/o exception");
        exception.printStackTrace();
      }
    }

    public void writeExternal(ObjectOutput out) throws IOException
    {
      out.writeObject(_var);
      out.writeObject(_expr);
    }

    public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException
    {
      _var = (String) in.readObject();
      _expr = (String) in.readObject();
    }

    public void exportXML(BufferedWriter out, int n) throws IOException
    {
      SB_ProjectBar.writeTabs(out, n);
      out.write("<binding>");
      out.newLine();
      SB_ProjectBar.writeTabs(out, n + 1);
      out.write("<var>" + _var + "</var>");
      out.newLine();
      String expr = _expr;
  
      expr = expr.replaceAll("&", "&amp;");
      expr = expr.replaceAll("<", "&lt;");
      SB_ProjectBar.writeTabs(out, n + 1);
      out.write("<expr>" + expr + "</expr>");
      out.newLine();
      SB_ProjectBar.writeTabs(out, n);
      out.write("</binding>");
      out.newLine();
    }

    public int findOccurrences(Pattern pattern, String strReplace, SB_Polymorphism poly, SB_Drawable drawable) throws SB_CancelException
    {
      int total = 0;
      for (int i = 0; i <= 1; ++i)
      {
        StringBuffer sb = null;
        StringBuffer sb_next = null;
        int delta = 0;
        Matcher matcher = pattern.matcher(i == 0 ? _var : _expr);
        int length = (i == 0 ? 0 : _var.length() + 3);
        while (matcher.find())
        {
          ++total;
          SimBionicEditor editor = poly.getParent().getEditor();
          SB_OutputBar outputBar = SB_OutputBar.getInstance();
          SB_Output find = outputBar.getFind();
          SB_ProjectBar projectBar = (SB_ProjectBar) ComponentRegistry.getProjectBar();
          SB_Catalog catalog = projectBar.getCatalog();
          find.addFindLine(poly, drawable, this, length + matcher.start() + delta, length + matcher.end() + delta);

          if (strReplace != null)
          {
            int sel = find.getLines().size() - 1;
            find.setSel(sel);

            int n = JOptionPane.showConfirmDialog(projectBar, "Replace this occurrence?",
                                                  "Replace", JOptionPane.YES_NO_CANCEL_OPTION);
            if (n == JOptionPane.YES_OPTION)
            {
              if (sb == null)
              {
                sb = new StringBuffer();
                sb_next = new StringBuffer();
              }
              matcher.appendReplacement(sb, strReplace);
              delta = sb.length() - matcher.end();
              sb_next.setLength(0);
              sb_next.append(sb);
              matcher.appendTail(sb_next);
              poly.addToUndoStack();
              if (i == 0)
              {
                _var = new String(sb_next);
                ((SB_BindingsHolder) drawable).updateBindings();
              }
              else
                _expr = new String(sb_next);
              poly.setHighlighted(false);
              SB_TabbedCanvas tabbedCanvas = (SB_TabbedCanvas) ComponentRegistry.getContent();
              tabbedCanvas.getActiveCanvas().clearSingle();
              find.setSel(sel);
              poly.setModified(true);
            }
            else if (n == JOptionPane.CANCEL_OPTION)
              throw new SB_CancelException();
          }
        }
      }
 
      return total;
    }
	/**
	 * @return Returns the expr.
	 */
	public String getExpr() {
		return _expr;
	}
	/**
	 * @param expr The expr to set.
	 */
	public void setExpr(String expr) {
		_expr = expr;
	}
	/**
	 * @return Returns the var.
	 */
	public String getVar() {
		return _var;
	}
	/**
	 * @param var The var to set.
	 */
	public void setVar(String var) {
		_var = var;
	}
 }
